package com.lcx.core;

import com.lcx.annotation.Autowired;
import com.lcx.annotation.IOCValue;
import com.lcx.annotation.Init;
import com.lcx.filter.ScannerFilter;
import com.lcx.filter.impl.DefaultScannerFilter;
import com.lcx.listener.ClassLoadedListener;
import com.lcx.listener.ClassVistorAnnocationFack;
import com.lcx.util.CopyFileUtil;
import com.lcx.util.ReflectUtil;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * 该容器只会管理加有IOCValue注解的类，且被加载的类是通过IOCValue来实例化的，其他方式加载的不会被管理。
 * IOCValue会实例化带有Autowired注解的属性。 IOCValue中不允许出现重复的类 IOCValue中的类一般用于单利。
 * 
 * @author Shisan
 *
 */
public class Scanner {

	/**
	 * 自动扫描只支持一种类型对应一个实例，一个类型多个实例将被替换！
	 */
	private static Map<Class<?>, Object> beans = new HashMap<>();

	private static Map<String,Object> nameBeans = new HashMap<>();

	/**
	 * 类扫描过滤器
	 */
	private static List<ScannerFilter> filters = new ArrayList<>(2);

	private static List<String> outScannerPath = new ArrayList<>(4);

	/**
	 * 每成功装载一个bean时调用
	 */
	private static List<ClassLoadedListener> loaddingListners = new ArrayList<>(2);

	private static ClassVistorAnnocationFack classVistorAnnocationFack = new ClassVistorAnnocationFack();

	private volatile static boolean hasCreated = false;

	public static void createContext(){
		createContext(null);
	}
	public synchronized static void createContext(List<String> path){
		if(hasCreated) return;
		hasCreated=true;
		if(path != null && !path.isEmpty()){
			outScannerPath.addAll(path);
		}
		filters.add(new DefaultScannerFilter());
		initBeans();
		initHandles();
		try {
			initField();
		} catch (Exception e) {
			e.printStackTrace();
		}
		outScannerPath = null;
		filters = null;
		loaddingListners = null;
	}

	public static void addOutScannerPath(String path){
		outScannerPath.add(path);
	}

	/**
	 * 初始化Bean
	 * 
	 * @param clazz
	 *            初始化的class
	 * @param param
	 *            构造方法中需要的参数
	 * @return 返回初始化的bean
	 * @throws Exception
	 */
	public static <T> T createBean(Class<?> clazz, Object[] param){
		if (clazz != null) {
			try{
				if (param == null || param.length == 0) {
					Object object = clazz.newInstance();
					putBeanInIoc(object);
					return (T) object;
				} else {
					Class<?>[] clArr = new Class[param.length];
					for (int i = 0; i < param.length; i++) {
						Object o = param[i];
						if (o != null) {
							clArr[i] = o.getClass();
						}
					}
					Constructor<?> constructor = clazz.getConstructor(clArr);
					Object object = constructor.newInstance(param);
					putBeanInIoc(object);
					return (T) object;
				}
			}catch (Exception e){
				throw new RuntimeException(e);
			}
		}
		return null;
	}

	/**
	 * 获取已有的bean，如果不存在则根据参数创建一个bean
	 * 
	 * @param clazz
	 *            要创建的类
	 * @param param
	 *            根据构造方法的参数创建
	 * @return
	 * @throws Exception
	 */
	public static <T> T getBeanOrCreate(Class<T> clazz, Object[] param){
		T t = (T) getBean(clazz);
		if (t == null) {
			try {
				t = createBean(clazz, param);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
			putBeanInIoc(t);
		}
		return t;
	}

	public static void putBean(String name,Object obj){
		putBeanWithName(name,obj);
	}

	public static void putBean(Object obj){
		putBean(obj,false);
	}
	public static void putBean(Object obj,boolean reinit){
		if(obj == null){
			return;
		}
		putBeanInIoc(obj);
		try{
			if(reinit){
				initField();
			}else{
				initField(obj.getClass());
				initHandle(obj);
			}
		}catch (Exception e){
			throw new RuntimeException(e);
		}
	}

	public static <T> T getBean(Class<T> clazz){
		T t = (T) beans.get(clazz);
		if (t != null) {
			if( t instanceof ClassCount){
				throw new RuntimeException(clazz.getName()+"在容器中已存在"+((ClassCount)t).getCount()+"份，未明确获取！");
			}
		}
		return t;
	}

	public static <T>T getBeanWithName(String name){
		return (T) nameBeans.get(name);
	}

	private static void initHandles(){
		if(!nameBeans.isEmpty()){
			nameBeans.values().stream().forEach(c->{
				initHandle(c);
			});
		}
	}
	private static void initHandle(Object c){
		List<Method> list = ReflectUtil.getMethods(c.getClass());
		if(!list.isEmpty()){
			list.stream().forEach(m->{
				Init init = m.getAnnotation(Init.class);
				if(init!=null){
					try{
						m.invoke(c,null);
					}catch (Exception e){
						e.printStackTrace();
					}
				}
			});
		}
	}

	private static void initBeans() {
		try {
			List<Class<?>> listClass = scanner();
			initBeans(listClass);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	private static void initBeans(Collection<Class<?>> listClass) throws Exception{
		for (Class<?> c : listClass) {
			if (c != null) {
				IOCValue ico = c.getAnnotation(IOCValue.class);
				if (ico != null) {
					String[] str = ico.param();
					if (str == null || str.length == 0) {
						putBeanInIoc(c.newInstance());
					} else {
						List<Object> valueList = new ArrayList<>();
						List<Class<?>> paramList = new ArrayList<>();
						for (String s : str) {
							if (s != null) {
								String[] arr = s.split("=");
								Object value = null;
								Class<?> clazz = Class.forName(arr[0]);
								if (clazz.equals(int.class) || clazz.equals(Integer.class)) {
									if (!"null".equals(arr[1])) {
										value = Integer.parseInt(arr[1]);
									}
								} else if (clazz.equals(double.class) || clazz.equals(Double.class)) {
									if (!"null".equals(arr[1])) {
										value = Double.parseDouble(arr[1]);
									}
								} else if (clazz.equals(float.class) || clazz.equals(Float.class)) {
									if (!"null".equals(arr[1])) {
										value = Float.parseFloat(arr[1]);
									}
								} else if (clazz.equals(char.class) || clazz.equals(Character.class)) {
									if (!"null".equals(arr[1])) {
										value = arr[1].charAt(0);
									}
								} else if (clazz.equals(boolean.class) || clazz.equals(Boolean.class)) {
									if (!"null".equals(arr[1])) {
										value = arr[1].equals("true");
									}
								} else if (clazz.equals(byte.class) || clazz.equals(Byte.class)) {
									if (!"null".equals(arr[1])) {
										value = Byte.parseByte(arr[1]);
									}
								} else if (clazz.equals(long.class) || clazz.equals(Long.class)) {
									if (!"null".equals(arr[1])) {
										value = Long.parseLong(arr[1]);
									}
								} else if (clazz.equals(short.class) || clazz.equals(Short.class)) {
									if (!"null".equals(arr[1])) {
										value = Short.parseShort(arr[1]);
									}
								} else if (clazz.equals(String.class)) {
									value = arr[1];
								} else {
									throw new Exception("不支持的数据类型：" + arr[0] + "，来自类：" + clazz.getName());
								}
								valueList.add(value);
								paramList.add(clazz);
							}
						}

						Constructor<?> constructor = null;
						try {
							constructor = c.getConstructor(paramList.toArray(new Class<?>[] {}));
						} catch (Exception e) {
							e.printStackTrace();
						}
						if (constructor == null) {
							throw new Exception("没有找到相应的构造方法");
						}
						Object obj = constructor.newInstance(valueList.toArray(new Object[] {}));
						putBeanInIoc(obj);
					}
				}
			}
		}

	}

	private static void initField() throws Exception{
		for (Object obj : nameBeans.values()) {
			initField(obj.getClass());
		}

	}

	private static void initField(Class clazz) throws Exception{
		List<Field> listField = ReflectUtil.getFields(clazz);
		Object obj = getBean(clazz);
		Autowired autowired;
		Resource resource = null;
		for (Field f : listField) {
			if((autowired = f.getAnnotation(Autowired.class) )!= null || ( resource= f.getAnnotation(Resource.class) )!= null){
				try{
					Object value = null;
					if(autowired != null){
						value = getBean(f.getType());
					}else if(resource != null){
						value = getBeanWithName(resource.name());
					}
					f.setAccessible(true);
					if (f.get(obj) == null) {
						f.set(obj, value);
					}
				}catch (Exception e){
					throw e;
				}finally {
					f.setAccessible(false);
				}
			}
		}

	}

	private static List<Class<?>> scanner() throws Exception {
		String classPath = System.getProperty("java.class.path");
		String []classPathArr;
		if(classPath.contains(";")){
			classPathArr = classPath.split(";");
		}else{
			classPathArr = classPath.split(":");
		}
		List<String> list = new ArrayList<>();
		for(String str:classPathArr){
			list.add(str);
		}
		if(!outScannerPath.isEmpty()){
			list.addAll(
					outScannerPath.stream().map(str->{
						List<File> listFiles = CopyFileUtil.eachPath(new File(str),f->f.getName().toLowerCase().endsWith(".jar"));
						if(listFiles == null) return new ArrayList<File>(1);
						return listFiles;
					}).flatMap(files->files.stream()).map(file->file.getPath()).collect(Collectors.toList())
			);
		}
		return scanner(list);
	}

	/**
	 * 扫描自身的所有的类
	 * 
	 * @return
	 * @throws Exception
	 */
	private static List<Class<?>> scanner(List<String> classPathList) throws Exception {
		List<Class<?>> listClass = new ArrayList<>();
		// URL url = new URL();
		Set<String> classNames = new HashSet<>();
		Set<File> classFiles = new HashSet<>();
		boolean hasVisited = false;
		//扫描记录所有的类
		for (String path : classPathList) {
			hasVisited = false;
			if (path.toLowerCase().endsWith(".jar")) {
				JarFile jarFile = new JarFile(path);
				Enumeration<JarEntry> entries = jarFile.entries(); // 获取JarFile的一个枚举类用于遍历jar所有的文件
				if (entries != null) {
					JarEntry je;
					try {
						String className;
						Class<?> clazz;
						while (entries.hasMoreElements()) {
							je = entries.nextElement();
							if (je.getName().toLowerCase().endsWith(".class")) {
								try {
									className = je.getName().replaceAll("/", ".").replaceAll("\\.class", "");
									if(!checkFilter(className)){
										InputStream is = jarFile.getInputStream(je);
										classVistorAnnocationFack.accept(is);
										if(classVistorAnnocationFack.checkHashAnnocation(IOCValue.class)){
//											listClass.add(clazz=loader
//													.loadClass(className));
											classNames.add(className);
											if(!hasVisited){
												classFiles.add(new File(path));
											}
//											whenLoaded(clazz);
										}
										hasVisited  =true;
									}
								} catch (Exception e) {
									e.printStackTrace();
								}
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				// 如果以/结尾，则是包，否则是类。

			} else {
				List<File> listFile = classFile(new File(path));
				String className;
				for (File f : listFile) {
					try {
						className = f.getAbsolutePath().substring(path.length() + 1)
								.replaceAll("\\\\", ".").replaceAll("/",".").replaceAll("\\.class", "");
						if(!checkFilter(className)){
							classVistorAnnocationFack.accept(new FileInputStream(f));
							if(classVistorAnnocationFack.checkHashAnnocation(IOCValue.class)){
								classNames.add(className);
								if(!hasVisited){
									classFiles.add(new File(path));
								}
//								Class<?> clazz = loader.loadClass(className);
//								listClass.add(clazz);
//								whenLoaded(clazz);
							}
							hasVisited=true;
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}
		}
		//初始化类信息
		if(!classNames.isEmpty()){
			ClassLoader loader = ClassLoader.getSystemClassLoader();
			URL []urls = new URL[classFiles.size()];
			ClassLoader outerClassLoader = new URLClassLoader(classFiles.stream().map(e->{
				try{
					return new URL("file:///"+e.getAbsolutePath().replaceAll("\\\\","/"));
				}catch (Exception e2){
					throw new RuntimeException(e2);
				}
			}).collect(Collectors.toList()).toArray(urls));
			classNames.stream().forEach(c->{
				Class<?> clazz;
				try{
					clazz=loader.loadClass(c);
				}catch (Exception e){
					try{
						clazz=outerClassLoader.loadClass(c);
					}catch (Exception e2){
						throw new RuntimeException(e2);
					}
				}
				listClass.add(clazz);
				whenLoaded(clazz);
			});
		}
		return listClass;
	}

	private static List<File> classFile(File file) {
		List<File> listFile = new ArrayList<>();
		if (file.isDirectory()) {
			File fs[] = file.listFiles();
			if (fs != null) {
				for (File f : fs) {
					List<File> list = classFile(f);
					if (list != null) {
						listFile.addAll(list);
					}
				}
			}
		} else {
			if (file.getName().toLowerCase().endsWith(".class")) {
				listFile.add(file);
			}
		}
		return listFile;
	}

	public static void putOutClasses(Collection<Class<?>> listClass) throws Exception{
		initBeans(listClass);
	}

	public static Set<Map.Entry<String,Object>> getAllBeans(){
		return nameBeans.entrySet();
	}

	private static boolean checkFilter(String clazzName){
		boolean skip = false;
		for(ScannerFilter filter:filters){
			if(filter.scanner(clazzName)){
				skip = true;
				break;
			}
		}
		return skip;
	}

	private static void whenLoaded(Class<?> clazz){
		for(ClassLoadedListener loadedListener:loaddingListners){
			loadedListener.loaded(clazz);
		}
	}

	private static void putBeanInIoc(Object obj){
		if(obj == null) throw new RuntimeException("对象实例为空！");
		Class<?> clazz = obj.getClass();
		String className = getSimpleClassName(clazz);
		putBeanWithName(className,obj);
	}

	private static void putBeanWithName(String className,Object obj){
		if(className == null || className.trim().isEmpty()) throw new RuntimeException("类别名不能为空！");
		if(obj == null) throw new RuntimeException("对象实例为空！");
		Object o = nameBeans.get(className);
		if(o != null){
			throw new RuntimeException("类别名重复："+className);
		}
		nameBeans.put(className,obj);
		Class<?> clazz = obj.getClass();
		o = beans.get(clazz);
		if(o == null){
			beans.put(clazz,obj);
		}else{
			if(o instanceof ClassCount){
				ClassCount c = (ClassCount) o;
				c.setCount(c.getCount()+1);
			}else{
				beans.put(clazz,new ClassCount());
			}
		}
	}

	private static String getSimpleClassName(Class<?> clazz){
		IOCValue iocValue = clazz.getAnnotation(IOCValue.class);
		String className;
		if(iocValue == null || iocValue.name().trim().isEmpty()){
			className = clazz.getSimpleName().substring(0,1).toLowerCase()+clazz.getSimpleName().substring(1);
		}else{
			className = iocValue.name();
		}
		return className.isEmpty()?null:className;
	}

	static class ClassCount{
		private int count = 1;

		public int getCount() {
			return count;
		}

		public void setCount(int count) {
			this.count = count;
		}
	}

}
